﻿using System;
using System.IO;
using System.Web;
using System.Xml;
using System.Xml.Xsl;
using Altairis.Nemesis.Gallery.Configuration;

namespace Altairis.Nemesis.Gallery {
    public class DirectoryListingHandler : IHttpHandler {

        private GallerySection config;

        public DirectoryListingHandler() {
            this.config = GallerySection.GetCurrentOrDefault();
        }

        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context) {
            // Check if handler is enabled
            if (!this.config.DirectoryListing.Enabled) context.Response.SendCustomErrorPage(403, "Forbidden", "Disabled in server configuration.");

            // Check if it's request for directory listing
            string path = context.Request.PhysicalPath;
            if (!System.IO.Directory.Exists(path)) context.Response.SendCustomErrorPage(403, "Forbidden", "Directory listing required for invalid or non-existent path, most likely due to server configuration error.");

            // Create XML document with list of files and folders
            XmlDocument listing = this.CreateDirectoryListing(path);

            // Get template file name
            string templateFileName = context.Server.MapPath(this.config.DirectoryListing.Template);

            if (System.IO.File.Exists(templateFileName)) {
                // XSL template found -- transform
                XslCompiledTransform tran = new XslCompiledTransform();
                tran.Load(templateFileName);
                tran.Transform(listing,
                    new ConfiguredXsltArgumentList(this.config.DirectoryListing.VaryByParams),
                    context.Response.OutputStream);
                context.Response.ContentType = "text/html";
            }
            else {
                // XSL template not found -- send raw XML
                context.Response.ContentType = "text/xml";
                using (XmlWriter w = new XmlTextWriter(context.Response.OutputStream, System.Text.Encoding.UTF8)) {
                    listing.Save(w);
                }
            }

            // Setup caching 
            if (config.DirectoryListing.CacheTimeout > 0) {
                context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
                context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(config.DirectoryListing.CacheTimeout));
                context.Response.AddFileDependency(path);
                context.Response.AddFileDependency(templateFileName);
                context.Response.Cache.SetLastModifiedFromFileDependencies();
                context.Response.Cache.SetETagFromFileDependencies();
                if (config.DirectoryListing.VaryByParams) context.Response.Cache.VaryByParams["*"] = true;
            }
        }

        // Helper methods

        private XmlDocument CreateDirectoryListing(string path) {
            // Get parent folder information
            DirectoryInfo parentFolder = new DirectoryInfo(path);

            // Create root element
            XmlDocument doc = new XmlDocument();
            doc.AppendChild(doc.CreateElement("directoryListing"));
            if (!parentFolder.Exists) return doc; // folder not found
            doc.DocumentElement.SetAttribute("title", parentFolder.Name.Replace('_', ' '));

            // Check if this folder is web root
            bool isRoot = parentFolder.FullName.TrimEnd('\\').Equals(System.Web.HttpContext.Current.Request.PhysicalApplicationPath.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase);
            doc.DocumentElement.SetAttribute("isRoot", isRoot.ToString());

            // Add virtual and physical paths
            doc.DocumentElement.SetAttribute("virtualPath", HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath);
            doc.DocumentElement.SetAttribute("physicalPath", HttpContext.Current.Request.PhysicalPath);

            // List subfolders matching folderMask
            foreach (DirectoryInfo subFolder in parentFolder.GetDirectories(this.config.DirectoryListing.FolderMask)) {
                // Exclude system folders
                if (this.config.DirectoryListing.ExcludeSystemFolders) {
                    if (subFolder.Name.StartsWith("App_", ".", "_")) continue;
                    if (subFolder.Name.Equals("bin", StringComparison.OrdinalIgnoreCase)) continue;
                }

                // Create <folder> element
                XmlElement folder = doc.CreateElement("folder");
                folder.SetAttribute("title", subFolder.Name.Replace('_', ' '));
                folder.SetAttribute("name", subFolder.Name);
                folder.SetAttribute("creationTime", subFolder.CreationTime.ToString("o"));
                folder.SetAttribute("lastWriteTime", subFolder.LastWriteTime.ToString("o"));

                // Append it to list
                doc.DocumentElement.AppendChild(folder);
            }

            // List files matching fileMask
            foreach (FileInfo subFile in parentFolder.GetFiles(this.config.DirectoryListing.FileMask)) {
                // Create <file> element
                XmlElement file = doc.CreateElement("file");
                file.SetAttribute("title", Path.GetFileNameWithoutExtension(subFile.Name).Replace('_', ' '));
                file.SetAttribute("name", subFile.Name);
                file.SetAttribute("creationTime", subFile.CreationTime.ToString("o"));
                file.SetAttribute("lastWriteTime", subFile.LastWriteTime.ToString("o"));
                file.SetAttribute("size", subFile.Length.ToString());

                // Append it to list
                doc.DocumentElement.AppendChild(file);
            }

            return doc;
        }

    }
}
